/*
 * kernel_info.c
 *
 * Estructura de datos representando información de kernel; con operaciones
 * para adquisición de datos desde el sistema de archivos /proc, y 
 * operaciones de "pretty-printing".
 *
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "data_format.h"
#include "version_info.h"
#include "cpu_info.h"
#include "statistic_info.h"
#include "lexer.h"

#include "kernel_info.h"

/* Estructura interna para KernelInfo */
struct _kernel_info_t {
	bstring proc_dir;

	bstring hostname;
	time_t date;
	CpuInfo cpu;
	VersionInfo kernel;
	StatisticInfo stats;
	time_t uptime;
};

/*
 * Rutinas internas para recolección de información
 *
 * Todas las funciones en esta sección tienen el siguiente estilo
 *     static <T> get_<xxx> (void)
 *
 * Donde <xxx> es el nombre de un campo de KernelInfo, y <T> es el tipo de
 * <xxx>
 * get_xxx lee de /proc la información, y devuelve un valor (con memoria
 * fresca en los casos de que <T> sea un puntero).
 */

#define STR_ERROR "ERROR"

static bstring get_hostname (const char *proc_dir) {
	bstring result = NULL;
	bstring path = NULL;
	FILE *f = NULL;
	
	assert (proc_dir != NULL);
	path = bfromcstr (proc_dir);
	bcatcstr (path, "/sys/kernel/hostname");
	f = fopen ((char *)path->data, "r");
	bdestroy (path);
	
	if (f != NULL) {
		Lexer *l = lexer_new (f);
		lexer_next_to (l, "\n");
		result = lexer_item (l);
		lexer_destroy (l);
		fclose (f);
	}
	if (result == NULL) {
		result = bfromcstr (STR_ERROR);
	}
	assert (result != NULL);

	return result;
}

static time_t get_uptime (const char *proc_dir) {
	time_t result = 0;
	bstring s = NULL;
	FILE *f = NULL;
	bstring path = NULL;
	
	assert (proc_dir != NULL);
	path = bfromcstr (proc_dir);
	bcatcstr (path, "/uptime");
	f = fopen ((char *)path->data, "r");
	bdestroy (path);
	
	if (f != NULL) {
		Lexer *l = lexer_new (f);
		lexer_next (l, DIGIT);
		s = lexer_item (l);
		result = atoi ((char *)s->data);
		bdestroy (s);
		lexer_destroy (l);
		fclose (f);
	}
	
	return result;
}

/*
 * Funciones de impresión
 *
 * Todas las funciones de esta sección tiene la siguiente signatura:
 *     static void print_<xxx> (const KernelInfo *self, FILE * out)
 * donde <xxx> es una de las palabras reconocidas por el pretty printer
 * (con la excepción de la palabra "", que se asocia a `print_dollar').
 * print_<xxx> imprime en `out' una versión formateada de <xxx> sacando los
 * datos de `self'.
 */

static void print_dollar (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fputc ('$', out);
}

static void print_hostname (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fputs ((char *)self->hostname->data, out);
}

static void print_date (const KernelInfo *self, FILE *out) {
	char *t = NULL; /* No usamos bstring aca... son todas funciones de la
	                  libc y todas áreas de memoria estáticas. */
	assert (self != NULL && out != NULL);
	t = ctime (&(self->date));
	/* El -1 de abajo evita la impresión del `\n' que ctime agrega al final */
	fwrite (t, sizeof (char), strlen (t) - 1, out);
}

static void print_cpu_type (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fputs ((char *)self->cpu.type->data, out);
}

static void print_cpu_model (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fputs ((char *)self->cpu.model->data, out);
}

static void print_kernel_major (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%d", self->kernel.major);
}

static void print_kernel_minor (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%d", self->kernel.minor);
}

static void print_kernel_svn (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%d", self->kernel.subversion);
}

static void print_kernel_tag (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fputs ((char *)self->kernel.tag->data, out);
}

static void print_uptime_s (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%ld", self->uptime);
}

static void print_uptime_f (const KernelInfo *self, FILE *out) {
	bstring s = NULL;
	
	assert (self != NULL && out != NULL);
	
	s = format_time (self->uptime);
	fputs ((char *)s->data, out);
	bdestroy (s);
}

static void print_cputime_user (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%.2f", self->stats.cpu_user / 1000.0);
}

static void print_cputime_system (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%.2f", self->stats.cpu_system / 1000.0);
}

static void print_cputime_idle (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%.2f", self->stats.cpu_idle / 1000.0);
}

static void print_context_switches (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%ld", self->stats.context_switches);
}

static void print_total_processes (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%ld", self->stats.process_count);
}

static void print_boot_date (const KernelInfo *self, FILE *out) {
	char *t; /* No usamos bstring aca... son todas funciones de la libc y todas
	            areas de memoria estaticas. */

	assert (self != NULL && out != NULL);

	t = ctime (&(self->stats.boot_date));
	/* El -1 de abajo evita la impresión del `\n' que ctime agrega al final */
	fwrite (t, sizeof (char), strlen (t) - 1, out);
}

static void print_disk_requests (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%ld", self->stats.disk_requests);
}

static void print_mem_available (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%ld", self->stats.memory_available);
}

static void print_mem_total (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%ld", self->stats.memory_total);
}

static void print_load_average_1 (const KernelInfo *self, FILE *out) {
	assert (self != NULL && out != NULL);
	fprintf (out, "%.2f", self->stats.load_average_1);
}

/*
 * Definición de campos imprimibles
 * El tipo que sigue, y la tabla posterior permiten agregar campos
 * facilmente.
 */

typedef struct field_table_element_t {
	const char *label;
	void (*printfunc) (const KernelInfo *, FILE *);
} field_table_element;

static field_table_element field_table[] = {
	{"",                 print_dollar},
	{"hostname",         print_hostname},
	{"date",             print_date},
	{"cpu_type",         print_cpu_type},
	{"cpu_model",        print_cpu_model},
	{"kernel_major",     print_kernel_major},
	{"kernel_minor",     print_kernel_minor},
	{"kernel_svn",       print_kernel_svn},
	{"kernel_tag",       print_kernel_tag},
	{"uptime_s",         print_uptime_s},
	{"uptime_f",         print_uptime_f},
	{"cputime_user",     print_cputime_user},
	{"cputime_system",   print_cputime_system},
	{"cputime_idle",     print_cputime_idle},
	{"context_switches", print_context_switches},
	{"total_processes",  print_total_processes},
	{"boot_date",        print_boot_date},
	{"disk_requests",    print_disk_requests},
	{"mem_available",    print_mem_available},
	{"mem_total",        print_mem_total},
	{"load_average_1",   print_load_average_1},
	{NULL, NULL} /* Siempre tiene que estar esto al final de la tabla! */
};

static void print_field (const KernelInfo *self, FILE *out, const char *format, size_t p, size_t q) {
/*
 * Muestra en `out' el valor de el campo de `self' asociado a format[p:q]
 */
	int i = 0;
	
	assert (self != NULL && out != NULL && format != NULL);
	assert (p < strlen (format) && q <= strlen (format));
	assert (p <= q);
	
	/* Buscamos en la tablita...*/
	while (field_table[i].label!=NULL &&
	       strncmp (field_table[i].label, format+p, q-p) != 0)
		i++;
	/* ...y si encontramos algo, llamamos a la función de impresión */
	if (field_table[i].label != NULL && format[q] != '\0')
		field_table[i].printfunc (self, out);
	else {
		/* Sino, imprimimos $palabra$ literalmente */
		fputc ('$', out);
		fwrite (format+p, sizeof (char), q-p,  out);
		if (format[q] != '\0') {
			fputc (format[q], out);
		}
	}
}

/* Funciones públicas */

KernelInfo *kernel_info_new (const char *proc_dir) {
	KernelInfo *result = NULL;
	
	assert (proc_dir != NULL);
	
	result = calloc (1, sizeof (*result));
	assert (result!=NULL);
	
	/* Recolectar información */
	result->hostname = get_hostname (proc_dir);
	result->date = time (NULL);
	result->cpu = cpu_info_new (proc_dir);
	result->kernel = version_info_new (proc_dir);
	result->stats = statistic_info_new (proc_dir);
	result->uptime = get_uptime (proc_dir);
	result->proc_dir = bfromcstr (proc_dir);

	return result;
}

void kernel_info_destroy (KernelInfo *self) {
	assert (self != NULL);

	bdestroy (self->hostname);
	self->cpu = cpu_info_destroy (self->cpu);
	version_info_destroy (self->kernel);
	bdestroy (self->proc_dir);
	free (self);
}

void kernel_info_update (KernelInfo *self) {

	assert (self != NULL);
	/* Hostname, version de kernel, y CPUs no cambian */
	self->date = time (NULL);
	self->uptime = get_uptime ((char *)self->proc_dir->data);
	statistic_info_destroy (self->stats);
	self->stats = statistic_info_new ((char *)self->proc_dir->data);
    
}

void kernel_info_print (const KernelInfo *self, FILE *out, const char *format) {
	size_t p = 0;
	size_t q = 0;
	
	assert (self != NULL);
	assert (out != NULL);
	assert (format != NULL);
	
	while (format[p] != '\0') {
		if (format[p] != '$') {
			/* Caracter normal, pasa derecho */
			fputc (format[p], out);
			p++;
		} else {
			/* Encontramos un $palabra$ */
			q = p+1;
			while (format[q]!='$' && format[q]!='\0') q++;
			print_field (self, out, format, p+1, q);
			p = q+1;
		}
	}
}

